/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cl_particle.cpp - Particle script parsing and loading
//


#include "cl_local.h"
#include "client.h"


struct prtScript_t {
	char					name[MAX_PATH_LENGTH];

	char					source[MAX_PATH_LENGTH];
	int						line;

	char					*buffer;
	int						size;

	prtScript_t				*nextHash;
};

static prtScript_t			*cl_particleScriptsHashTable[PARTICLES_HASH_SIZE];
static particleSource_t		*cl_particlesHashTable[PARTICLES_HASH_SIZE];

static particleSource_t		*cl_particles[MAX_PARTICLES];
static int					cl_numParticles;


// TODO: Add operators?


/*
 ==============================================================================

    PARTICLE PARSING

 ==============================================================================
*/


/*
 ==================
 CL_ParseStageCount
 ==================
*/
static bool CL_ParseStageCount (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;

	if (!PS_ReadToken(script, &token))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'material' in particle '%s'\n", particle->name);
		return false;
	}

	stage->count = Str_ToInteger(token.string);

	return true;
}

/*
 ==================
 CL_ParseStageMaterial
 ==================
*/
static bool CL_ParseStageMaterial (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;

	if (!PS_ReadToken(script, &token))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'material' in particle '%s'\n", particle->name);
		return false;
	}

	stage->material = R_RegisterMaterialNoMip(token.string);

	return true;
}

/*
 ==================
 CL_ParseStageTime
 ==================
*/
static bool CL_ParseStageTime (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;

	if (!PS_ReadToken(script, &token))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'time' in particle '%s'\n", particle->name);
		return false;
	}

	stage->time = Str_ToInteger(token.string);

	return true;
}

/*
 ==================
 CL_ParseStageColor
 ==================
*/
static bool CL_ParseStageColor (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;
	int		i;

	for (i = 0; i < 3; i++)
	{
		if (!PS_ReadFloat(script, &stage->color[i]))
		{
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'color' in particle '%s'\n", particle->name);
			return false;
		}

		if (i < 2)
		{
			PS_ReadToken(script, &token);
			if (Str_ICompare(token.string, ","))
			{
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'color' in particle '%s'\n", token.string, particle->name);
				return false;
			}
		}
	}

	return true;
}

/*
 ==================
 CL_ParseStageFadeColor
 ==================
*/
static bool CL_ParseStageFadeColor (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;
	int		i;

	for (i = 0; i < 3; i++)
	{
		if (!PS_ReadFloat(script, &stage->fadeColor[i]))
		{
			Com_Printf(S_COLOR_YELLOW "WARNING: missing expression parameters for 'fadeColor' in particle '%s'\n", particle->name);
			return false;
		}

		if (i < 2)
		{
			PS_ReadToken(script, &token);
			if (Str_ICompare(token.string, ","))
			{
				Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead for 'fadeColor' in particle '%s'\n", token.string, particle->name);
				return false;
			}
		}
	}

	return true;
}

/*
 ==================
 CL_ParseStageAlpha
 ==================
*/
static bool CL_ParseStageAlpha (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;

	if (!PS_ReadToken(script, &token))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'alpha' in particle '%s'\n", particle->name);
		return false;
	}

	stage->alpha = Str_ToFloat(token.string);

	return true;
}

/*
 ==================
 CL_ParseStageFadeAlpha
 ==================
*/
static bool CL_ParseStageFadeAlpha (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;

	if (!PS_ReadToken(script, &token))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'fadeAlpha' in particle '%s'\n", particle->name);
		return false;
	}

	stage->fadeAlpha = Str_ToFloat(token.string);

	return true;
}

/*
 ==================
 CL_ParseStageRadius
 ==================
*/
static bool CL_ParseStageRadius (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;

	if (!PS_ReadToken(script, &token))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'radius' in particle '%s'\n", particle->name);
		return false;
	}

	stage->radius = Str_ToFloat(token.string);

	return true;
}

/*
 ==================
 CL_ParseStageFadeRadius
 ==================
*/
static bool CL_ParseStageFadeRadius (script_t *script, particleSource_t *particle, prtStage_t *stage)
{
	token_t token;

	if (!PS_ReadToken(script, &token))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'fadeRadius' in particle '%s'\n", particle->name);
		return false;
	}

	stage->fadeRadius = Str_ToFloat(token.string);

	return true;
}


// ============================================================================


struct particleGlobalCmd_t {
	const char	*name;
	bool	    (*parseCmd)(script_t *, particleSource_t *);
};

struct particleStageCmd_t {
	const char	*name;
	bool	    (*parseCmd)(script_t *, particleSource_t *, prtStage_t *);
};

static particleGlobalCmd_t	cl_particleGlobalCmds[] = {
	{NULL,						NULL}
};

static particleStageCmd_t	cl_particleStageCmds[] = {
	{"count",					CL_ParseStageCount},
	{"material",				CL_ParseStageMaterial},
	{"time",					CL_ParseStageTime},
	{"color",					CL_ParseStageColor},
	{"fadeColor",				CL_ParseStageFadeColor},
	{"alpha",					CL_ParseStageAlpha},
	{"fadeAlpha",				CL_ParseStageFadeAlpha},
	{"radius",					CL_ParseStageRadius},
	{"fadeRadius",				CL_ParseStageFadeRadius},
	{NULL,						NULL}
};

/*
 ==================
 CL_ParseGlobalCommand
 ==================
*/
static bool CL_ParseGlobalCommand (script_t *script, const char *command, particleSource_t *particle)
{
	particleGlobalCmd_t   *cmd;

	for (cmd = cl_particleGlobalCmds; cmd->name; cmd++)
	{
		if (!Str_ICompare(cmd->name, command))
			return cmd->parseCmd(script, particle);
	}

	Com_Printf(S_COLOR_YELLOW "WARNING: unknown global command '%s' in particle '%s'\n", command, particle->name);
	return false;
}

/*
 ==================
 CL_ParseStageCommand
 ==================
*/
static bool CL_ParseStageCommand (script_t *script, const char *command, particleSource_t *particle, prtStage_t *stage)
{
	particleStageCmd_t   *cmd;

	for (cmd = cl_particleStageCmds; cmd->name; cmd++)
	{
		if (!Str_ICompare(cmd->name, command))
			return cmd->parseCmd(script, particle, stage);
	}

	Com_Printf(S_COLOR_YELLOW "WARNING: unknown stage command '%s' in particle '%s'\n", command, particle->name);
	return false;
}

/*
 ==================
 CL_ParseParticle
 ==================
*/
static bool CL_ParseParticle (script_t *script, particleSource_t *particle)
{
	token_t		 token;
	prtStage_t   *stage;

	// Parse the material
	if (!PS_ExpectTokenString(script, &token, "{", true))
	{
		Com_Printf(S_COLOR_YELLOW "WARNING: expected '{', found '%s' instead in particle '%s'\n", token.string, particle->name);
		return false;
	}

	while (1){
		if (!PS_ReadToken(script, &token))
		{
			Com_Printf(S_COLOR_YELLOW "WARNING: no concluding '}' in particle '%s'\n", particle->name);
			return false;	// End of script
		}

		if (!Str_Compare(token.string, "}"))
			break;			// End of particle

		// Parse a stage
		if (!Str_Compare(token.string, "{"))
		{
			// Create a new stage
			if (particle->numStages == MAX_PARTICLE_STAGES)
			{
				Com_Printf(S_COLOR_YELLOW "WARNING: MAX_PARTICLE_STAGES hit in particle '%s'\n", particle->name);
				return false;
			}

			stage = &particle->stages[particle->numStages++];

			// Parse it
			while (1){
				if (!PS_ReadToken(script, &token))
				{
					Com_Printf(S_COLOR_YELLOW "WARNING: no matching '}' in particle '%s'\n", particle->name);
					return false;	// End of script
				}

				if (!Str_Compare(token.string, "}"))
					break;			// End of stage

				// Parse a stage command
				if (!CL_ParseStageCommand(script, token.string, particle, stage))
					return false;
			}

			continue;
		}

		// Parse a global command
		if (!CL_ParseGlobalCommand(script, token.string, particle))
			return false;
	}

	return true;
}

/*
 ==================
 CL_ParseParticleFile
 ==================
*/
static void CL_ParseParticleFile (script_t *script, const char *name)
{
	token_t		 token;
	prtScript_t	 *prtScript;
	const char   *buffer, *end;
	int			 size;
	uint		 hashKey;

	while (1){
		// Parse the name
		if (!PS_ReadToken(script, &token))
			break;   // End of script

		// Parse the script
		buffer = script->text;
		PS_SkipBracedSection(script, 0);
		end = script->text;

		if (!buffer)
			buffer = script->buffer;
		if (!end)
			end = script->buffer + script->size;

		size = end - buffer;

		// Allocate a new particle script
		prtScript = (prtScript_t *)Mem_Alloc(sizeof(prtScript_t), cls.particleMemoryPool);

		// Fill it in
		Str_Copy(prtScript->name, token.string, sizeof(prtScript->name));

		Str_SPrintf(prtScript->source, sizeof(prtScript->source), "particles/%s", name);
		prtScript->line = token.line;

		prtScript->buffer = (char *)Mem_Alloc(size + 1, cls.particleMemoryPool);
		memcpy(prtScript->buffer, buffer, size);
		prtScript->buffer[size] = 0;

		prtScript->size = size;

		// Add to hash table
		hashKey = Str_HashKey(prtScript->name, PARTICLES_HASH_SIZE, false);

		prtScript->nextHash = cl_particleScriptsHashTable[hashKey];
		cl_particleScriptsHashTable[hashKey] = prtScript;
	}
}


/*
 ==============================================================================

    PARTICLE REGISTRATION AND CREATION

 ==============================================================================
*/


/*
 ==================
 CL_NewParticle
 ==================
*/
static particleSource_t *CL_NewParticle ()
{
	particleSource_t   *particle;

	if (cl_numParticles == MAX_PARTICLES)
		Com_Error(ERR_DROP, "CL_NewParticle: MAX_PARTICLES hit");

	// Allocate the particle
	cl_particles[cl_numParticles++] = particle = (particleSource_t *)Mem_Alloc(sizeof(particleSource_t), cls.particleMemoryPool);
	memset(particle, 0, sizeof(particleSource_t));

	// Allocate the stages
	particle->stages = (prtStage_t *)Mem_Alloc(MAX_PARTICLE_STAGES * sizeof(prtStage_t), cls.particleMemoryPool);
	memset(particle->stages, 0, MAX_PARTICLE_STAGES * sizeof(prtStage_t));

	return particle;
}

/*
 ==================
 CL_CreateParticle
 ==================
*/
static particleSource_t *CL_CreateParticle (const char *name, prtScript_t *prtScript)
{
	particleSource_t   *particle;
	script_t		   *script;

	// Create a new material
	particle = CL_NewParticle();

	// Fill it in
	Str_Copy(particle->name, name, sizeof(particle->name));

	// If we have a script, create an external particle
	if (prtScript)
	{
		particle->prtScript = prtScript;

		// Load the script text
		script = PS_LoadScriptMemory(prtScript->name, prtScript->buffer, prtScript->size, prtScript->line);
		if (!script)
			return NULL; //R_CreateDefaultMaterial(name, type, surfaceParm);

		PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

		// Parse it
		if (!CL_ParseParticle(script, particle))
		{
			PS_FreeScript(script);

			return NULL; //R_CreateDefaultMaterial(name, type, surfaceParm);
		}

		PS_FreeScript(script);

		return particle;
	}

	// TODO: create an implicit material?

	return particle;
}

/*
 ==================
 CL_LoadParticle
 ==================
*/
static particleSource_t *CL_LoadParticle (particleSource_t *newParticle)
{
	particleSource_t   *particle;
	uint			   hashKey;

	particle = newParticle;

	// Add to hash table
	hashKey = Str_HashKey(particle->name, PARTICLES_HASH_SIZE, false);

	particle->nextHash = cl_particlesHashTable[hashKey];
	cl_particlesHashTable[hashKey] = particle;

	return particle;
}

/*
 ==================
 CL_FindParticle
 ==================
*/
particleSource_t *CL_FindParticle (const char *name)
{
	particleSource_t    *particle;
	prtScript_t			*prtScript;
	uint				hashKey;

	if (!name || !name[0])
		Com_Error(ERR_DROP, "R_FindParticle: NULL material name");

	if (Str_Length(name) >= MAX_PATH_LENGTH)
		Com_Error(ERR_DROP, "R_FindParticle: particle name exceeds MAX_PATH_LENGTH");

	// Check if it already exists
	hashKey = Str_HashKey(name, PARTICLES_HASH_SIZE, false);

	for (particle = cl_particlesHashTable[hashKey]; particle; particle = particle->nextHash)
	{
		if (!Str_ICompare(particle->name, name))
			return particle;
	}

	// Check if a script already exists for this particle
	for (prtScript = cl_particleScriptsHashTable[hashKey]; prtScript; prtScript = prtScript->nextHash)
	{
		if (!Str_ICompare(prtScript->name, name))
			break;
	}

	// Create the particle
	particle = CL_CreateParticle(name, prtScript);

	// Load it in
	return CL_LoadParticle(particle);
}

/*
 ==================
 CL_RegisterParticle
 ==================
*/
particleSource_t *CL_RegisterParticle (const char *name)
{
	return CL_FindParticle (name);
}

/*
 ==============================================================================

    CONSOLE COMMANDS

 ==============================================================================
*/


void CL_ListParticles_f ()
{
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 CL_InitParticles
 ==================
*/
void CL_InitParticles ()
{
	script_t	 *script;
	char		 name[MAX_PATH_LENGTH];
	const char   **fileList;
	int			 numFiles;
	int			 i;

	Com_Printf("Initializing Particles\n");

	// Load and parse .mtr files
	fileList = FS_ListFiles("particles", ".prt", true, &numFiles);

	for (i = 0; i < numFiles; i++)
	{
		// Load the script file
		Str_SPrintf(name, sizeof(name), "particles/%s", fileList[i]);
		Com_Printf("...loading '%s'\n", name);

		script = PS_LoadScriptFile(name);
		if (!script){
			Com_Printf(S_COLOR_YELLOW "WARNING: couldn't load '%s'\n", name);
			continue;
		}

		PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

		// Parse it
		CL_ParseParticleFile(script, fileList[i]);

		// Free the script file
		PS_FreeScript(script);
	}

	FS_FreeFileList(fileList);

	// TODO: Create internal particle?
}

/*
 ==================
 CL_ShutdownParticles
 ==================
*/
void CL_ShutdownParticles ()
{
	particleSource_t   *particle;
	int				   i;

	// Delete all the particles
	for (i = 0; i < cl_numParticles; i++)
	{
		particle = cl_particles[i];

		Mem_Free(particle);
	}

	// Clear particle scripts and particle lists
	memset(cl_particleScriptsHashTable, 0, sizeof(cl_particleScriptsHashTable));

	memset(cl_particlesHashTable, 0, sizeof(cl_particlesHashTable));
	memset(cl_particles, 0, sizeof(cl_particles));

	cl_numParticles = 0;
}